<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>作业调度算法</title>
    <script src="./js/jquery-1.8.3.min.js"></script>
    <style>
        *{
            padding: 0;
            margin: 0;
        }
        body{
            width: 100wh;
            height: 100vh;
            overflow: hidden;
            /* background: url('./imgs/bg.png') center no-repeat; */
        }
        .Display_data{
            text-align: center;
        }
        .Display_data .make button{
            margin: auto;
        }
        .form table{
            background: #0f6d6048;
            margin: auto;
            margin-top: 40px;
        }
        th td{
            width: 205px;
            height: 41px;
        }
        button{
            width: 100%;
            height: 100%;
        }
        .Datavisual{
            width: 600px;
            height: 100px;
            float: left;
            overflow: hidden;
            text-align: center;
            font-size: 25px;
            line-height: 100px;
            margin-top: 40px;
            margin-left: 33.8%;
            /* background: rgb(197, 49, 49,.5); */
            /* position: relative; */
        }
        .child{
            /* position: absolute; */
            transition:2s;
            background: red;
            float: left;
            height: 100%;
        }
    </style>
</head>
<body>
    <div class="Display_data">
        <div class="form">
            <table border="1px">
                <thead>
                    <tr>
                        <th >进程名</th>
                        <th>到达时间</th>
                        <th>服务时间</th>
                        <!-- 隐藏内容 -->
                        <th class="Hidden_content" hidden>开始执行时间</th>
                        <th class="Hidden_content" hidden>完成时间</th>
                        <th class="Hidden_content" hidden>周转时间</th>
                        <th class="Hidden_content" hidden>带权周转时间</th>
                        <th class="Hidden_content" hidden>等待时间</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>
                            <input class="process_name" type="text">
                        </td>
                        <td>
                            <input class="time_of_arrival" type="number">
                        </td>
                        <td>
                            <input class="service_hours" type="number">
                        </td>
                        <td class="Hidden_content startExecutionTime" hidden ></td>
                        <td class="Hidden_content completeTime" hidden></td>
                        <td class="Hidden_content turnaroundTime" hidden ></td>
                        <td class="Hidden_content weightedTurnaroundTime" hidden></td>
                        <td class="Hidden_content writTime" hidden></td>
                    </tr>
                </tbody>
                <tfoot>
                    <tr>
                        <td>
                            <button id="add" onclick="add()">添加一行</button>
                        </td>
                        <td>
                            <button id="del" onclick="del()">删除一行</button>
                        </td>
                        <td>
                            <button type="button"  onclick="javascript:location.reload()">重置</button>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <button onclick="operatingForFSFC()">FSFC先到先服务</button> 
                        </td>
                        <td>
                            <button onclick="operatingForSJF()">SJF短作业优先</button> 
                        </td>
                        <td>
                            <button onclick="experiment()">这个按钮没用</button>
                        </td>
                    </tr>
                </tfoot>
            </table>
            <div class="shou"></div>
        </div> 
        <!-- 数据可视化 -->
        <div class="Datavisual"></div>      
    </div>
    <script>
        //实验
        function experiment(){
            if($('.Datavisual').css('width') == '0px'){
                $('.Datavisual').css({
                    'transition-duration':'2s',
                    'background':'red',
                    'width':'600px'
                });
            }
            if($('.Datavisual').css('width') == '600px'){
                $('.Datavisual').css({
                    'transition-duration':'0s',
                    'width':'0px',
                    'background':''
                });
            }
        }
        //增加一行
        function add(){
            $('table tbody').append('<tr><td><input class="process_name" type="text"></td><td><input class="time_of_arrival"  type="number"></td><td><input class="service_hours"  type="number"></td><td class="Hidden_content startExecutionTime" hidden></td><td class="Hidden_content completeTime" hidden></td><td class="Hidden_content turnaroundTime" hidden></td><td class="Hidden_content weightedTurnaroundTime" hidden></td><td class="Hidden_content writTime" hidden></td></tr>');
        }
        //删除一行
        function del(){
            var arr = $('table tbody tr');
            if(arr.length > 0){
                var last = arr.length;
                arr[last-1].remove();
            }
        }
        //计算 FSFC算法实现
        function operatingForFSFC(){
            if(init()){ //先对用户输入的信息进行读取
                var complete_array = FSFC();//完成数组，接收返回的进程队列
                //调用方法，处理过的数据，呈现给用户
                showData(complete_array);
                Datavisual(complete_array);
                //对使用过的全局变量初始化
                complete_array.length = 0;
                PCB.length = 0;
                time = '';
            } 
            
        }
        //计算 SJF算法实现
        function operatingForSJF(){
            if(init()){ //先对用户输入的信息进行读取
                var complete_array = SJF();//完成数组，接收返回的进程队列
                //调用方法，处理过的数据，呈现给用户
                showData(complete_array);
                Datavisual(complete_array);
                complete_array.length = 0;
                PCB.length = 0;
                time = '';
            } 
        }
        //FSFC要实现的主要脚本
        const PCB = new Array();    //进程队列     
        var time = '';              //初始化系统时间
        //用于计算算法实现的平均值，使用过后，要回复初始值
        //初始值赋值为 ''，防止于计算出来的数值冲突
        var averageTurnaroundTime = '';  //平均周转时间
        var averageWaitingTime = '';     //平均等待时间
        var averageWeightedTurnaroundTime = '';  //平均带权周转时间
        var executionOrder = '';        //执行顺序
        //获取到用户输入的数值，当数值不合法时，弹窗警告并退出
        function init(){
            var process_name =  $('.process_name');
            var time_of_arrival = $('.time_of_arrival');
            var service_hours = $('.service_hours');
            var length = $('.form table tbody tr').length;
            //对输入的数据进行检索
            if(!examination(process_name)){
                return false;
            }
            if(!examination(time_of_arrival)){
                return false;
            }
            if(!examination(service_hours)){
                return false;
            }
            //将各个输入框的值读取到PCB数组中
            for(var i = 0; i < length; i++){
                var prescss = {};     //进程
                prescss.process_name = process_name[i].value;
                prescss.time_of_arrival = time_of_arrival[i].value * 1; //转化为数字
                prescss.service_hours = service_hours[i].value * 1;
                PCB.push(prescss);
            }
            return true;
        }
        //检查数据是否为空
        function examination(object){
            for(var i =0;i < object.length;i++){
                if(object[i].value == ''){
                    alert("输入的数据不能为空，请检查");
                    return false;
                }
                if(object[i].value < 0){
                    alert("请不要输入负数");
                    return false;
                }
            }
            return true;
        }
        //发现PCB数组中最先到达的进程
        function findMinForTime_of_arrival(){
            //程序正常运行，不被别人恶意更改代码执行的顺序，PCB 的长度不可能会是0
            if(PCB.length == 0){
                alert("PCB进程处理队列为空");
                return;
            }
            var min = PCB[0].time_of_arrival;
            //迭代遍历，获得最先到达进程的时间（获取最小值）
            PCB.forEach(prescss =>{
                if(min > prescss.time_of_arrival){
                    min = prescss.time_of_arrival;//
                }
            });
            //根据最小值，获取并返回对应数组下标
            for(var i =0 ;i<PCB.length;i++){
                if(PCB[i].time_of_arrival == min){
                    return i;
                }
            }
        }
        //FSFC先到先服务算法的实现
        //  每次获得先到的进程，然后取出对应的进程，并放到完成队列中，循环执行，直到PCB中的进程全部放到完成队列中
        function FSFC(){
            const  carry_out = new Array(); //完成队列
            var index = 0;
            while(PCB.length != 0){
                index = findMinForTime_of_arrival();
                var prescss = PCB[index];   //获取到对应进程
                if(time == ''){ //系统刚开始处理进程
                    //第一个被处理的进程 开始执行时间 = 该进程的到达时间
                    prescss.startExecutionTime = prescss.time_of_arrival;
                    //完成时间 = 开始执行时间 + 运行（服务）时间
                    prescss.completeTime = prescss.startExecutionTime + prescss.service_hours;
                    //周转时间 = 完成时间 - 到达时间
                    prescss.turnaroundTime = prescss.completeTime - prescss.startExecutionTime;
                    //带权周转时间 = 周转时间 / 运行（服务）时间
                    prescss.weightedTurnaroundTime = prescss.turnaroundTime / prescss.service_hours;
                    //等待时间 = 周转时间 - 运行(服务)时间
                    prescss.writTime = prescss.turnaroundTime - prescss.service_hours;
                    //此时系统时间 = 该进程的完成时间
                    time = prescss.completeTime;
                }else{//出去第一个先到达的进程，其余进程的处理
                    //如果 第一个 / 前一个 进程的完成时间小于 第二个 / 此代码块正在处理的进程
                    if(time < prescss.time_of_arrival){
                        //前一个进程的完成时间 = 系统时间 
                        //前一个进程处理完了，但后一个进程还没到达，系统空闲
                        var idle = prescss.time_of_arrival - time;  //得到空闲时间是多少
                        time += idle;   //想到于系统度过空闲出的进程
                    }
                    //排除第一个进程的处理 后面的进程的处理方法
                    //开始时间 = 系统时间
                    prescss.startExecutionTime = time;  //这里是于第一个进程处理公式不一样的地方
                    //后面的处理步骤（处理最后一步修正系统时间不一致之外），与第一个进程的处理步骤一致
                    //完成时间 = 开始执行时间 + 服务（运行）时间
                    prescss.completeTime = prescss.startExecutionTime + prescss.service_hours;
                    //周转时间 = 完成时间 - 到达时间
                    prescss.turnaroundTime = prescss.completeTime - prescss.time_of_arrival;
                    //带权周转时间 = 周转时间 / 运行（服务）时间
                    prescss.weightedTurnaroundTime = prescss.turnaroundTime / prescss.service_hours;
                    //等待时间 = 周转时间 - 运行时间
                    prescss.writTime = prescss.turnaroundTime - prescss.service_hours;
                    //修正系统的时间 排除第一个进程 系统的时间 = 原先的系统时间 + 正在处理进程的运行时间
                    time += prescss.service_hours;
                    //这里方法不一，也可以是：系统时间 = 此进程的完成时间
                }
                //将处理完成的进程添加到完成队列之中
                prescss.status = "创建态";  //将进程的状态 写入到进程对象中   这里FSFC并没有用，主要是同步后面的SJF
                carry_out.push(prescss);
                //将处理过的进程从PCB队列之中移出
                PCB.splice(index,1);    //达到队列往下进行的作用，（关键一步）
            }
            return carry_out;
        }
        

        //非抢占式断进程优先服务
        function SJF(){
            const carry_out = new Array();  //定义完成队列
            while(PCB.length != 0){
                var index = findMinForTime_of_arrival();    //先找到进程队列中到达时间最小的时间
                var process = PCB[index];   //得到对应的进程
                //系统刚开始处理进程队列
                if(time == ''){
                    //第一个被处理的进程块   开始时间 = 到达时间
                    process.startExecutionTime = process.time_of_arrival;
                    //完成时间 = 开始运行时间 + 运行（服务）时间
                    process.completeTime = process.startExecutionTime + process.service_hours;
                    //周转时间 = 完成时间 - 到达时间
                    process.turnaroundTime = process.completeTime - process.time_of_arrival;
                    //带权周转时间 = 周转时间 / 运行（服务）时间
                    process.weightedTurnaroundTime = process.turnaroundTime / process.service_hours;
                    //等待时间 = 周转时间 - 运行时间
                    process.writTime = process.turnaroundTime - process.service_hours;
                    //此时的系统时间 = 该进程的完成时间
                    time = process.completeTime;
                    
                }else{
                    //如果前一个完成时间，小于到达时间，系统又空闲
                    if(time < process.time_of_arrival){
                        var idle = process.time_of_arrival - time;  //得到空闲出来的时间
                        time += idle;   //修正系统时间
                    }
                    //判断其他进程的状态
                    whetherToBlock(); 
                    //找到最短服务时间的进程下标
                    index = findMinForService_hours();
                    //获取最短服务的进程对象    
                    process = PCB[index];   //这个是本次循环要操作计算的对象
                    //排除第一个进程 开始执行时间 = 系统时间
                    process.startExecutionTime = time;
                    //完成时间 = 开始时间 + 服务（运行）时间
                    process.completeTime = process.startExecutionTime + process.service_hours;
                    //周转时间 = 完成时间 - 到达时间
                    process.turnaroundTime = process.completeTime - process.time_of_arrival;
                    //带权周转时间 = 周转时间 / 运行（服务）时间
                    process.weightedTurnaroundTime = process.turnaroundTime / process.service_hours;
                    //等待时间 = 周转时间 - 运行时间
                    process.writTime = process.turnaroundTime - process.service_hours;
                    //此时的系统时间 = 原来的系统时间 + 该进程的服务（运行）时间
                    time += process.service_hours;
                }
                
                //让进程进入到终止状态，这里是为了区分其他没有 创建态  阻塞态
                //其实没有什么作用
                process.status = "终止态";
                //将该线程 添加到 完成队列（其实是数组）中去
                carry_out.push(process);
                //将该线程从PCB队列中移除，因为已经让PCB处理过了
                PCB.splice(index,1);    //关键一步
            }
            return carry_out;   //将装了处理过后的进程数组返回，一供后续函数使用
        }

        //对进程中各个进程的状态进行判断，将符合状态的进程该为阻塞态
        function whetherToBlock(){
            if(time == ''){
                //系统还未处理进程，各个进程处在还没有创建的状态，程序中不会出现
                return;
            }
            //迭代循环
            PCB.forEach(process =>{
                if(time >= process.time_of_arrival){
                    //如果此时系统时间大于或等于进程的到达时间，说明进程已经准备好被PCB处理了
                    //将进程的状态修改为 阻塞态 可能不符合各个进程的真实状态  
                    //但这里只是作为标记，方便SJF算法的实现，不纠结这个
                    process.status = "阻塞态";
                }
            });
        }

        //返回队列中服务时间最短的进程下标
        // 但要在进程出于阻塞状态时 才符合条件
        function findMinForService_hours(){
            var index = findMinForTime_of_arrival();    //运行此函数时，系统已经处理过至少一个进程了
            var min = PCB[index].service_hours;//返回到达时间最短的进程的服务时间
            //找到最小服务时间
            //迭代循环
            PCB.forEach(process =>{
                //先判断进程的状态是否是阻塞态
                if(process.status == "阻塞态"){
                    //是阻塞态 才有权比较是否是最短服务时间
                    if(min > process.service_hours){
                        min = process.service_hours;
                    }
                }
            });
            //根据找到的最小的服务时间，返回对应的进程下标
            for(var i =0;i<PCB.length;i++){
                if(PCB[i].service_hours == min){
                    return i;   //返回对应的下标
                }
            }
        }
        //将处理过后的数据显示给用户
        function showData(dataArr){
            //判断传入的数据是否没有进程   程序正常运行 不会出现
            if(dataArr.length <= 0){
                return;
            }
            //显示表格
            var all_hidden = $('.Hidden_content');
            for(var i = 0;i<all_hidden.length;i++){
                $(all_hidden[i]).removeAttr('hidden');
            }
            
            //获取行数
            var number_of_lines = $('tbody tr').length;
            //获取列
            var process_name =  $('.process_name');
            var time_of_arrival = $('.time_of_arrival');
            var service_hours = $('.service_hours');
            var startExecutionTime = $('.startExecutionTime');
            var completeTime = $('.completeTime');
            var turnaroundTime = $('.turnaroundTime');
            var weightedTurnaroundTime = $('.weightedTurnaroundTime');
            var writTime = $('.writTime');
            //将carray_out中装的信息 显示给用户
            for(var i =0;i<number_of_lines;i++){
                var prescss = dataArr[i];
                // var index = i * column.length;
                process_name[i].value = prescss.process_name;
                time_of_arrival[i].value = prescss.time_of_arrival;
                service_hours[i].value = prescss.service_hours;
                startExecutionTime[i].innerHTML = (prescss.startExecutionTime);
                completeTime[i].innerHTML = (prescss.completeTime);
                turnaroundTime[i].innerHTML = (prescss.turnaroundTime);
                weightedTurnaroundTime[i].innerHTML = (prescss.weightedTurnaroundTime);
                writTime[i].innerHTML = (prescss.writTime);
            }
            //计算各个平均值和显示进程的执行顺序
            Average(dataArr);
        }
        //计算平均值和显示进程的执行顺序
        function Average(dataArr){
            //传入的数组不合法
            if(dataArr.length <= 0){
                return;
            }
            //计算算法
            var turnaroundTime = 0; //总周转时间
            var weightedTurnaroundTime = 0; //总带权周转时间
            var waitingTime = 0;    //总等待时间
            dataArr.forEach(process =>{
                executionOrder += process.process_name;
                executionOrder += "-->";
                turnaroundTime += process.turnaroundTime;
                weightedTurnaroundTime += process.weightedTurnaroundTime;
                waitingTime += process.writTime;
            });
            //对各个平均值进行赋值
            averageTurnaroundTime = turnaroundTime / dataArr.length;
            averageWeightedTurnaroundTime = weightedTurnaroundTime / dataArr.length;
            averageWaitingTime = waitingTime / dataArr.length;
            //显示计算出来的平均值
            if($('p.text').length != 5){
                //页面还没有添加p元素
                $('.shou').append('<p style="color:red" class="text">执行顺序：'+executionOrder+'</p>');
                $('.shou').append('<p style="color:red" class="text">平均周转时间：'+averageTurnaroundTime+'</p>');
                $('.shou').append('<p style="color:red" class="text">平均带权周转时间：'+averageWeightedTurnaroundTime+'</p>');
                $('.shou').append('<p style="color:red" class="text">平均等待时间：'+averageWaitingTime+'</p>');
                $('.shou').append('<p style="color:red" class="text">总耗时：'+time+'</p>');
            }else{
                //页面已经有p元素了
                var msgArr = $('p.text');
                $(msgArr[0]).text('执行顺序：'+executionOrder);
                $(msgArr[1]).text('平均周转时间：'+averageTurnaroundTime);
                $(msgArr[2]).text('平均带权周转时间：'+averageWeightedTurnaroundTime);
                $(msgArr[3]).text('平均等待时间：'+averageWaitingTime);
                $(msgArr[4]).text('总耗时：'+time);
            }  
            //清空数据
            executionOrder = '';
            averageTurnaroundTime = '';
            averageWeightedTurnaroundTime = '';
            averageWaitingTime = '';
        }
        //数据可视
        function Datavisual(dataArr){
            $($('.Datavisual').get(0)).css('banckground','');
            $('.Datavisual').text(' ');
            //这里函数调用时，time各个数据还没有被初始化
            //函数的功能是显示各个进程的服务时间，出入的数组是完成队列的数据
            //函数开始先将class= child的div清除掉
            var arr = $('.child');
            for(var i =0;i<arr.length;i++){
                arr[i].remove();
            }
            //重新创建
            for(var i =0;i<dataArr.length;i++){
                $('.Datavisual').append('<div class="child"></div>');
            }
            //显示数据
            var unit = 600/time; //单位
            var child = $('.child');
            for(var i =0;i<dataArr.length;i++){
                var process = dataArr[i];//进程
                var rgba = randomColor();    
                $(child[i]).css({
                    'background':'rgba('+rgba.r+','+rgba.g+','+rgba.b+','+rgba.a+')',
                    'width':process.service_hours*unit+'px',
                });
                $(child[i]).text(process.process_name);
            }
            
        }
        // console.log(randomColor().r);
        //返回随机生成的rgba颜色值
        function randomColor(){
            var color = {};
            color.r = (Math.ceil(255*Math.random()));
            color.g = (Math.ceil(255*Math.random()));
            color.b = (Math.ceil(255*Math.random()));
            color.a = (0.1+(Math.random()*0.9)).toFixed(1)*1;
            return color;
        }
    </script>
</body>
</html>